<?php

/**
 * HotPhpServerModel
 * 
 * 火速PHP服务器抽象类
 *
 * @author Judson-Leigh
 * @version $Id:$
 */
abstract class HotPhpServerModel
{

	/**
	 * 命令行提示信息
	 * 
	 * @property string
	 */
    protected $_usage = '';
    
    /**
     * 命令行参数配置
     *
     * @property Array
     */
    protected $_params = array(
        0 => array(                     //配置文件参数
            'key' => '-f',              //参数关键字
            'name' => 'Config Name',    //参数名称
            'isMust' => true,           //是否有必须
            'hasValue' => true,         //是否有变量
            'value' => null,            //变量值
        ),        
        1 => array(                     //配置文件参数
            'key' => '-q',              //参数关键字
            'name' => 'quit',           //参数名称
            'isMust' => false,          //是否有必须
            'hasValue' => false,        //是否有变量
            'value' => null,            //变量值
        ),
    );
    
    /**
     * 配置信息
     *
     * @property Array
     */
    public $_config = array();
    
    /**
     * 运行间隔（微妙）
     *
     * @property Array
     */
    protected $_runInterval = 1;
    
    /**
     * 共享内存句柄
     *
     * @property int
     */
    protected $_shmId = 0;
    
    /**
     * 最大日志池数
     *
     * @property int
     */
    protected $_logSize = 0;
    
    
    /**
     * 定时执行时间
     *
     * @property array
     */
    protected $_timingRun = array();

    private  $logisinited=false;//是否已完成log初始化

    private $configfile=false;//当前使用的config文件
    
    /**
     * 日志等级
     *
     * @property array
     */
    public static $LOG_LEVEL = array(
        'ALERT'   => 1,  // Alert: 报警
        'CRIT'    => 2,  // Critical: 紧要
        'ERR'     => 3,  // Error: 错误
        'WARN'    => 4,  // Warning: 警告
        'NOTICE'  => 5,  // Notice: 通知
        'INFO'    => 6,  // Informational: 一般信息
        'DEBUG'   => 7,  // Debug: 调试消息
    );
    
    /**
     * 共享内存分区配置
     * 
     * @property array
     */
    public static $SHM_CONFIG = array(
        'HEADER_SIZE'       => 6000,    // 头块尺寸（字节）
        'STATUS_NUMBER'     => 100,     // 状态变量最大个数
        'STATUS_FIELD_SIZE' => 50,      // 单状态变量内存大小（字节）
        'LOG_LINE_SIZE'     => 200,     // 单条日志记录内存大小（字节）
    );
    
    /**
     * 构造函数
     *
     * @return HotPhpServerModel 当前对象
     */
    public function __construct() {
    }
    
    /**
     * 获取参数
     * 
     * @param array $argv 传递给脚本的参数数组
     * @return bool 是否成功获取参数（null：不需要获取参数）
     */
    public function getParam($argv) {
        if (empty($this->_params) == false && is_array($this->_params)) {
            //循环命令行参数配置
            foreach ($this->_params as $keyParam => $param) {
                //循环传入的参数 
                foreach ($argv as $nArgvKey => $strArgvValue) {
                    if ($strArgvValue == $param['key']) {
                        //参数相同
                        if ($param['hasValue'] == true) {
                            //需要获取变量
                            $strParamVal = @$argv[$nArgvKey + 1]; //获取参数变量
                            //判断参数是否输入
                            if ($strParamVal === null || $strParamVal === false || $strParamVal === '' || substr($strParamVal, 0, 1) == '-') {
                                throw new Exception('The ' . $param['name'] . ' [' . $param['key'] . '] parameter is not the input value!');                                
                            }
                            $this->_params[$keyParam]['value'] = $strParamVal;
                        } else {
                            //不需要获取变量
                            $this->_params[$keyParam]['value'] = true;
                        }
                    }
                }
                //判断必填参数是否输入
                if ($param['isMust'] == true && $this->_params[$keyParam]['value'] == null) {
                    //必填参数未输入
                    throw new Exception('The ' . $param['name'] . ' [' . $param['key'] . '] parameter is not input!');
                }
            }
        } else {
            //不需要获取参数
            return null;
        }
        return true;
    }
    
    /**
     * 取命令行提示信息
     *
     * @return string 命令行提示信息
     */
    public function getUsage() {
        return str_replace('[FILE]', $this->execFile, $this->_usage);
    }
    
    /**
     * 取配置文件
     *
     * @param string $configFile 配置文件名
     * @return void
     */
    public function getConfig($configFile) {
        $configFile = trim($configFile);
        //判断配置文件名是否输入
        if (empty($configFile) == true) {
            throw new Exception('The configuration file is null!');
        }
        //判断配置文件名是否存在
        if (is_file($configFile) == false) {
            throw new Exception('The configuration file does not exist!');
        }
        
        //取配置信息
        $arrayIni = @parse_ini_file($configFile);
        if($arrayIni === false || is_array($arrayIni) === false) {
            //取配置错误
            throw new Exception('The configuration file is error!');
        }
        $this->_config = $arrayIni;
        $this->configfile=$configFile;
        return;
    }
    
    /**
     * 设置只能运行一次
     *
     * @param string $pidFile 进程号文件名
     * @return void
     */
    public function setRunOnce($pidFile) {
        //验证进程号文件是否存在
        if (is_file($pidFile)) {
            //进程号文件存在
            $nNowProcPid = intval(file_get_contents($pidFile));
        
            if ($nNowProcPid > 0) {
                //判断进程号文件是否在执行
                if( is_dir('/proc/'.$nNowProcPid) ) {
                    //当前文件已经被执行
                    throw new Exception('The current file is executed!');
                }
            }
        }
        
        $nNowProcPid = getmypid();	//获取当前进程号
        
        $nSize = file_put_contents($pidFile, $nNowProcPid);
        if ($nSize === FALSE) {
            //写入进程号文件错误
            throw new Exception('The pid could not be written!');            
        }
    }
    
    /**
     * 设置运行间隔
     * 
     * @param int $runInterval 运行间隔（秒）
     * @return void
     */
    public function setRunInterval($runInterval) {
        $this->_runInterval = intval($runInterval) * 1000000;
    }


    /**
     * 初始化日志
     *
     * @param int $logSize 日志池大小（不小于10条，不大于10000条）
     * @return void
     */
    public function initLog($logSize) {
        if(!$this->logisinited) {
            $this->logisinited=true;
            $logSize = intval($logSize);
            if ($logSize < 10) {
                $logSize = 10;
            } elseif ($logSize > 10000) {
                $logSize = 10000;
            }
            $this->_logSize = $logSize;

            $shm_key = @ftok($this->configfile, 't');

            if ($shm_key == -1) {
                $shm_key = @ftok(__FILE__, 't');
            }
            $shmopSize = ($logSize * self::$SHM_CONFIG['LOG_LINE_SIZE']) + self::$SHM_CONFIG['HEADER_SIZE'] + (self::$SHM_CONFIG['STATUS_NUMBER'] * self::$SHM_CONFIG['STATUS_FIELD_SIZE']);  //共享内存尺寸

            //创建共享内存
            $shm_id = @shmop_open($shm_key, "c", 0666, $shmopSize);
            if ($shm_id === false) {

                $shm_id = @shmop_open($shm_key, "w", 0666, $shmopSize);
                shmop_delete($shm_id);
                $shm_id = @shmop_open($shm_key, "c", 0666, $shmopSize);
            }
            $this->_shmId = $shm_id;

            if ($this->getStatusValue('logNex') == null) {
                $this->setStatusValue('logNex', 0);
            }

        }
    }
    
    /**
     * 设置状态变量
     *
     * @param string $key 变量名 (不能超过12个字符)
     * @param string $value 变量值 (不能超过50个字符)
     * @return bool 是否设置成功
     */
    public function setStatusValue($key, $value) {

        if ($this->_shmId == 0) {
            return false;
        }

        $key = trim($key);
        if ($key == '') {

            return false;
        }

        if(strlen($key)>12)
        {
            echo 'setStatusValue(),$key is out,your key is ['.$key.']';
            exit();
        }

        $key = substr($key, 0, 12);
        $value = substr(trim($value), 0, self::$SHM_CONFIG['STATUS_FIELD_SIZE']);
                
        //取出内存头信息
        $strShmHeader = trim(shmop_read($this->_shmId,0,self::$SHM_CONFIG['HEADER_SIZE']));
        if ($strShmHeader == '') {
             $arrayShmHeader = array(
                 $key => 0,                 
             ); 
        } else {            
            $arrayShmHeader = @unserialize($strShmHeader);
            if ($arrayShmHeader == false) {
                $arrayShmHeader = array(
                    $key => 0,
                );
            }
            if (isset($arrayShmHeader[$key]) == false) {
                //当前变量值是否存在
                $arrayShmHeader[$key] = count($arrayShmHeader);
            }
        }
        
        //序列化头数组
        $strShmHeader = serialize($arrayShmHeader);
        $strShmHeader = str_pad($strShmHeader, self::$SHM_CONFIG['HEADER_SIZE']);
                
        //写共享内存头块
        shmop_write($this->_shmId, $strShmHeader, 0);
        
        //写入变量值
        $value = str_pad($value, self::$SHM_CONFIG['STATUS_FIELD_SIZE']);
        shmop_write($this->_shmId, $value, self::$SHM_CONFIG['HEADER_SIZE'] + ($arrayShmHeader[$key] * self::$SHM_CONFIG['STATUS_FIELD_SIZE']));
        return true;
    }
    
    /**
     * 取状态变量
     *
     * @param string $key 变量值 (不能超过12个字符)
     * @return string 状态变量值 （未设置返回NULL）
     */
    public function getStatusValue($key) {
        if ($this->_shmId == 0) {
            return false;
        }
        $key = trim($key);
        if ($key == '') {
            return false;
        }
        $key = substr($key, 0, 12);
        
        //取出内存头信息
        $strShmHeader = trim(shmop_read($this->_shmId,0,self::$SHM_CONFIG['HEADER_SIZE']));
        if ($strShmHeader == '') {
            return null;
        } else {
            $arrayShmHeader = @unserialize($strShmHeader);
            if ($arrayShmHeader == false) {
                return null;
            }
        }
        
        if(isset($arrayShmHeader[$key]) == false) {
            return null;
        }
        $num = (self::$SHM_CONFIG['HEADER_SIZE'] + ($arrayShmHeader[$key] * self::$SHM_CONFIG['STATUS_FIELD_SIZE']));
        //读共享内存状态变量块
        return trim(shmop_read($this->_shmId, self::$SHM_CONFIG['HEADER_SIZE'] + ($arrayShmHeader[$key] * self::$SHM_CONFIG['STATUS_FIELD_SIZE']), self::$SHM_CONFIG['STATUS_FIELD_SIZE']));
    }
    
    /**
     * 加入日志
     *
     * @param int $logLevel 日志等级
     * @param string $logInfo 日志信息
     * @return bool 是否添加成功
     */
    public function addLog($logLevel, $logInfo) {
        if ($this->_shmId == 0) {
            //日志尚未初始化
            return false;
        }
        
        //取下个日志下标
        $nNextLogNum = $this->getStatusValue('logNex');       

        //截取前180个字符，合并日志
        $logInfo = date('y-m-d H:i:s') . '(L' . $logLevel . ')' . substr($logInfo,0,180);
        
        $logInfo = str_pad($logInfo, self::$SHM_CONFIG['LOG_LINE_SIZE']);
    
        shmop_write($this->_shmId, $logInfo, self::$SHM_CONFIG['HEADER_SIZE'] + (self::$SHM_CONFIG['STATUS_NUMBER'] * self::$SHM_CONFIG['STATUS_FIELD_SIZE']) + ($nNextLogNum * self::$SHM_CONFIG['LOG_LINE_SIZE']));
    
        $nNextLogNum ++;
        
        if ($nNextLogNum >= $this->_logSize) {
            $nNextLogNum = 0;
        }
        //写入下个日志下标
        $this->setStatusValue('logNex', $nNextLogNum);

        if($this->getStatusValue('SessionLogs')<$this->_logSize) {
            //设置当前有效日志条数
            $this->setStatusValue('SessionLogs', intval($this->getStatusValue('SessionLogs') + 1));
        }
        if($logLevel==self::$LOG_LEVEL['ERR'])
            $this->setStatusValue('T_Err_Count',$this->getStatusValue('T_Err_Count')+1);//日志中的错误数量
        return true;
    }
    
    /**
     * 初始化定时服务
     *
     * @param array $times 运行的时间（每天）     
     * @return boot 设置是否成功
     */
    public function initTimingService($times) {
        if (empty($times) || is_array($times) == false) {
            return false;
        }
        
        $this->_timingRun = $times;
        return true;
    }
    
    /**
     * 服务初始化（通过重构此方法，实现服务初始化）
     *
     * @return void
     */
    public function init() {
    }
    
    /**
     * 服务运行
     * 
     * @return void 
     */
    public function run() {
        //验证配置文件
        try {
            $this->getConfig($this->_params[0]['value']);
        } catch (Exception $e) {
            echo $e->getMessage();
            echo $this->getUsage();
            exit;
        }
        
        //验证进程号文件
        if(isset($this->_config['pid_file']) == false) {
            echo "The configuration file is missing pid_file!\n";
            exit;
        }
        // 日志大小
        if (isset($this->_config['log_size']) == false) {
            echo "The configuration file is missing log_size!\n";
            exit();
        }
        
        if ($this->_params[1]['value'] == true) {
            //关闭进程
            
            $pidFile = $this->_config['pid_file'];
            //验证进程号文件是否存在
            if (is_file($pidFile)) {
                //进程号文件存在
                $nNowProcPid = intval(file_get_contents($pidFile));
            
                if ($nNowProcPid > 0) {
                    //判断进程号文件是否在执行
                    if( is_dir('/proc/'.$nNowProcPid) ) {
                        //当前文件已经被执行,关闭进程
                        posix_kill($nNowProcPid, SIGKILL);
                    } else {
                        echo "The process is not executed!\n";
                        exit;
                    }
                } else {
                    echo "The process is not executed!\n";
                    exit;
                }
            } else {
                echo "The process is not executed!\n";
                exit;
            }
             
            echo "Service shutdown!|\n";
            exit;
        }
        
        try {
            //设置只能一次运行
            $this->setRunOnce($this->_config['pid_file']);
        } catch (Exception $e) {
            echo $e->getMessage() . "\n";
            exit;
        }
        
        //初始化服务
        $this->initLog($this->_config['log_size']);
        $this->init();

        
        if ($this->_runInterval == 0) {
            //运行间隔至少1微妙
            $this->_runInterval = 1;
        }
                
        //定时服务状态
        $arrayTimingServiceStatus = array();
        
        foreach ($this->_timingRun as $key=>$time) {
            //初始化清理状态数组
            $arrayTimingServiceStatus[$key]['time'] = $time;	    //指定执行时间
            $arrayTimingServiceStatus[$key]['lastRunDate'] = '';	//最后执行日期
            $arrayTimingServiceStatus[$key]['lastRunTime'] = '';	//最后执行时间
        }
        
        //安装中断信号处理器
        pcntl_signal(SIGTERM , SIG_IGN);
        pcntl_signal(SIGHUP , SIG_IGN);
        pcntl_signal(SIGUSR1 , SIG_IGN);

        $this->setStatusValue('S_Ex_Count',0);//轮询处理未处理的异常数
        $this->setStatusValue('T_Ex_Count',0);//定时处理未处理的异常数
        $this->setStatusValue('T_Err_Count',0);//日志中的错误数量
        if($this->getStatusValue('SessionLogs')==null)
            $this->setStatusValue('SessionLogs',0);//初始化本次操作系统会话的logs数量

        while(true) {
            
            usleep($this->_runInterval);
            try {
                $this->service();
            }catch (Exception $e) {
                $e_msg='service() by ['.__METHOD__.'] throw exception:'.$e->getMessage();
                $trace=$e->getTraceAsString();
                if(!empty($trace))
                    $e_msg.='|'.$trace;
                echo date('y-m-d H:i:s').':'.$e_msg;
                $this->addLog(self::$LOG_LEVEL['ERR'],$e_msg);
                $this->setStatusValue('S_Ex_Count',$this->getStatusValue('S_Ex_Count')+1);
                //exit;
            }
            
            if (empty($this->_timingRun) == false) {
                $timeNow = date("H:i:s");	//取当前时间
                $dateNow = date("Y-m-d");	//取当前日期            
                
                foreach ($arrayTimingServiceStatus as $key=>$rowRunStatus) {
                    
                    usleep(1);
                    
                    //判断当前时间是否大于定时执行时间
                    if ($rowRunStatus['time'] <= $timeNow && ($rowRunStatus['lastRunDate'] == '' || $rowRunStatus['lastRunDate'] < $dateNow)) {
                        //需要执行定时服务
                        try {
                            $this->timingService();
                        }catch (Exception $e) {
                            $e_msg='timingService() by ['.__METHOD__.'] throw exception:'.$e->getMessage();
                            $trace=$e->getTraceAsString();
                            if(!empty($trace))
                                $e_msg.='|'.$trace;
                            echo date('y-m-d H:i:s').':'.$e_msg;
                            $this->addLog(self::$LOG_LEVEL['ERR'],$e_msg);
                            $this->setStatusValue('T_Ex_Count',$this->getStatusValue('T_Ex_Count')+1);
                            //exit;
                        }

                        
                        //记录清理时间与日期
                        $arrayTimingServiceStatus[$key]['lastRunDate'] = $dateNow;
                        $arrayTimingServiceStatus[$key]['lastRunTime'] = $timeNow;
                        break;
                    }
                }
            }
        };
    }
    
    /**
     * 服务接口（通过重构此方法，实现服务业务逻辑）
     *
     * @return void
     */
    public function service() {
        echo "Hello World!\n";
    }
    
    /**
     * 定时服务接口（通过重构此方法，实现定时服务业务逻辑）
     *
     * @return void
     */
    public function timingService() {
        echo "Hello Big World!\n";
    }
    
}

?>